1 package org.apache.commons.betwixt;
2
3 /*
4 * ====================================================================
5 *
6 * The Apache Software License, Version 1.1
7 *
8 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
9 * reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in
20 * the documentation and/or other materials provided with the
21 * distribution.
22 *
23 * 3. The end-user documentation included with the redistribution, if
24 * any, must include the following acknowlegement:
25 * "This product includes software developed by the
26 * Apache Software Foundation (http://www.apache.org/)."
27 * Alternately, this acknowlegement may appear in the software itself,
28 * if and wherever such third-party acknowlegements normally appear.
29 *
30 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
31 * Foundation" must not be used to endorse or promote products derived
32 * from this software without prior written permission. For written
33 * permission, please contact apache@apache.org.
34 *
35 * 5. Products derived from this software may not be called "Apache"
36 * nor may "Apache" appear in their names without prior written
37 * permission of the Apache Group.
38 *
39 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
40 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
42 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
43 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
44 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
45 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
46 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
47 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
48 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
49 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
50 * SUCH DAMAGE.
51 * ====================================================================
52 *
53 * This software consists of voluntary contributions made by many
54 * individuals on behalf of the Apache Software Foundation. For more
55 * information on the Apache Software Foundation, please see
56 * <http://www.apache.org/>.
57 */
58
59 import java.beans.BeanDescriptor;
60 import java.beans.BeanInfo;
61 import java.beans.Introspector;
62 import java.beans.IntrospectionException;
63 import java.beans.PropertyDescriptor;
64 import java.lang.reflect.Method;
65 import java.net.URL;
66 import java.util.ArrayList;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.HashMap;
70
71 import org.apache.commons.logging.LogFactory;
72 import org.apache.commons.logging.Log;
73
74 import org.apache.commons.betwixt.expression.EmptyExpression;
75 import org.apache.commons.betwixt.expression.IteratorExpression;
76 import org.apache.commons.betwixt.expression.MethodExpression;
77 import org.apache.commons.betwixt.expression.MethodUpdater;
78 import org.apache.commons.betwixt.expression.StringExpression;
79 import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
80 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
81 import org.apache.commons.betwixt.strategy.DefaultNameMapper;
82 import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
83 import org.apache.commons.betwixt.strategy.NameMapper;
84 import org.apache.commons.betwixt.strategy.PluralStemmer;
85
86 /***
87 * <p><code>XMLIntrospector</code> an introspector of beans to create a
88 * XMLBeanInfo instance.</p>
89 *
90 * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
91 * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
92 * for a particular class, the <code>XMLBeanInfo</code> is cached.
93 * Later requests for the same class will return the cached value.</p>
94 *
95 * <p>Note :</p>
96 * <p>This class makes use of the <code>java.bean.Introspector</code>
97 * class, which contains a BeanInfoSearchPath. To make sure betwixt can
98 * do his work correctly, this searchpath is completely ignored during
99 * processing. The original values will be restored after processing finished
100 * </p>
101 *
102 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
103 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
104 * @version $Id: XMLIntrospector.java,v 1.10 2002/10/26 14:30:54 mvdb Exp $
105 */
106 public class XMLIntrospector {
107
108 /*** Log used for logging (Doh!) */
109 protected Log log = LogFactory.getLog( XMLIntrospector.class );
110
111 /*** should attributes or elements be used for primitive types */
112 private boolean attributesForPrimitives = false;
113
114 /*** should we wrap collections in an extra element? */
115 private boolean wrapCollectionsInElement = true;
116
117 /*** Is <code>XMLBeanInfo</code> caching enabled? */
118 boolean cachingEnabled = true;
119
120 /*** Maps classes to <code>XMLBeanInfo</code>'s */
121 protected Map cacheXMLBeanInfos = new HashMap();
122
123 /*** Digester used to parse the XML descriptor files */
124 private XMLBeanInfoDigester digester;
125
126 // pluggable strategies
127
128 /*** The strategy used to detect matching singular and plural properties */
129 private PluralStemmer pluralStemmer;
130
131 /*** The strategy used to convert bean type names into element names */
132 private NameMapper elementNameMapper;
133
134 /***
135 * The strategy used to convert bean type names into attribute names
136 * It will default to the normal nameMapper.
137 */
138 private NameMapper attributeNameMapper;
139
140 private boolean useBeanInfoSearchPath = false;
141
142 /*** Base constructor */
143 public XMLIntrospector() {
144 }
145
146 /***
147 * <p> Get the current logging implementation. </p>
148 */
149 public Log getLog() {
150 return log;
151 }
152
153 /***
154 * <p> Set the current logging implementation. </p>
155 */
156 public void setLog(Log log) {
157 this.log = log;
158 }
159
160 /***
161 * Is <code>XMLBeanInfo</code> caching enabled?
162 */
163 public boolean isCachingEnabled() {
164 return cachingEnabled;
165 }
166
167 /***
168 * Set whether <code>XMLBeanInfo</code> caching should be enabled.
169 */
170 public void setCachingEnabled(boolean cachingEnabled) {
171 this.cachingEnabled = cachingEnabled;
172 }
173
174 /***
175 * Flush existing cached <code>XMLBeanInfo</code>'s.
176 */
177 public void flushCache() {
178 cacheXMLBeanInfos.clear();
179 }
180
181 /*** Create a standard <code>XMLBeanInfo</code> by introspection
182 The actual introspection depends only on the <code>BeanInfo</code>
183 associated with the bean.
184 */
185 public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
186 if (log.isDebugEnabled()) {
187 log.debug( "Introspecting..." );
188 log.debug(bean);
189 }
190 return introspect( bean.getClass() );
191 }
192
193 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
194 The actual introspection depends only on the <code>BeanInfo</code>
195 associated with the bean.
196 */
197 public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
198 // we first reset the beaninfo searchpath.
199 String[] searchPath = null;
200 if (!useBeanInfoSearchPath)
201 {
202 searchPath = Introspector.getBeanInfoSearchPath();
203 Introspector.setBeanInfoSearchPath(new String[] { });
204 }
205
206 XMLBeanInfo xmlInfo = null;
207 if ( cachingEnabled ) {
208 // if caching is enabled, try in caching first
209 xmlInfo = (XMLBeanInfo) cacheXMLBeanInfos.get( aClass );
210 }
211 if (xmlInfo == null) {
212 // lets see if we can find an XML descriptor first
213 if ( log.isDebugEnabled() ) {
214 log.debug( "Attempting to lookup an XML descriptor for class: " + aClass );
215 }
216
217 xmlInfo = findByXMLDescriptor( aClass );
218 if ( xmlInfo == null ) {
219 BeanInfo info = Introspector.getBeanInfo( aClass );
220 xmlInfo = introspect( info );
221 }
222
223 if (xmlInfo != null) {
224 cacheXMLBeanInfos.put( aClass, xmlInfo );
225 }
226 } else {
227 log.trace("Used cached XMLBeanInfo.");
228 }
229
230 if (log.isTraceEnabled()) {
231 log.trace(xmlInfo);
232 }
233 if (!useBeanInfoSearchPath)
234 {
235 // we restore the beaninfo searchpath.
236 Introspector.setBeanInfoSearchPath(searchPath);
237 }
238
239 return xmlInfo;
240 }
241
242 /*** Create a standard <code>XMLBeanInfo</code> by introspection.
243 The actual introspection depends only on the <code>BeanInfo</code>
244 associated with the bean.
245 */
246 public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {
247 XMLBeanInfo answer = createXMLBeanInfo( beanInfo );
248
249 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
250 Class beanClass = beanDescriptor.getBeanClass();
251
252 ElementDescriptor elementDescriptor = new ElementDescriptor();
253 elementDescriptor.setLocalName( getElementNameMapper().mapTypeToElementName( beanDescriptor.getName() ) );
254 elementDescriptor.setPropertyType( beanInfo.getBeanDescriptor().getBeanClass() );
255
256 if (log.isTraceEnabled()) {
257 log.trace(elementDescriptor);
258 }
259
260 // add default string value for primitive types
261 if ( isPrimitiveType( beanClass ) ) {
262 elementDescriptor.setTextExpression( StringExpression.getInstance() );
263 elementDescriptor.setPrimitiveType(true);
264 }
265 else if ( isLoopType( beanClass ) ) {
266 ElementDescriptor loopDescriptor = new ElementDescriptor();
267 loopDescriptor.setContextExpression(
268 new IteratorExpression( EmptyExpression.getInstance() )
269 );
270 if ( Map.class.isAssignableFrom( beanClass ) ) {
271 loopDescriptor.setQualifiedName( "entry" );
272 }
273 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
274
275 /*
276 elementDescriptor.setContextExpression(
277 new IteratorExpression( EmptyExpression.getInstance() )
278 );
279 */
280 }
281 else {
282 List elements = new ArrayList();
283 List attributes = new ArrayList();
284
285 addProperties( beanInfo, elements, attributes );
286
287 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
288 if ( additionals != null ) {
289 for ( int i = 0, size = additionals.length; i < size; i++ ) {
290 BeanInfo otherInfo = additionals[i];
291 addProperties( otherInfo, elements, attributes );
292 }
293 }
294
295 int size = elements.size();
296 if ( size > 0 ) {
297 ElementDescriptor[] descriptors = new ElementDescriptor[size];
298 elements.toArray( descriptors );
299 elementDescriptor.setElementDescriptors( descriptors );
300 }
301 size = attributes.size();
302 if ( size > 0 ) {
303 AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
304 attributes.toArray( descriptors );
305 elementDescriptor.setAttributeDescriptors( descriptors );
306 }
307 }
308
309 answer.setElementDescriptor( elementDescriptor );
310
311 // default any addProperty() methods
312 XMLIntrospectorHelper.defaultAddMethods( this, elementDescriptor, beanClass );
313
314 return answer;
315 }
316
317
318 // Properties
319 //-------------------------------------------------------------------------
320
321 /*** Should attributes (or elements) be used for primitive types.
322 */
323 public boolean isAttributesForPrimitives() {
324 return attributesForPrimitives;
325 }
326
327 /*** Set whether attributes (or elements) should be used for primitive types. */
328 public void setAttributesForPrimitives(boolean attributesForPrimitives) {
329 this.attributesForPrimitives = attributesForPrimitives;
330 }
331
332 /*** @return whether we should we wrap collections in an extra element? */
333 public boolean isWrapCollectionsInElement() {
334 return wrapCollectionsInElement;
335 }
336
337 /*** Sets whether we should we wrap collections in an extra element? */
338 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
339 this.wrapCollectionsInElement = wrapCollectionsInElement;
340 }
341
342 /***
343 * @return the strategy used to detect matching singular and plural properties
344 */
345 public PluralStemmer getPluralStemmer() {
346 if ( pluralStemmer == null ) {
347 pluralStemmer = createPluralStemmer();
348 }
349 return pluralStemmer;
350 }
351
352 /***
353 * Sets the strategy used to detect matching singular and plural properties
354 */
355 public void setPluralStemmer(PluralStemmer pluralStemmer) {
356 this.pluralStemmer = pluralStemmer;
357 }
358
359 /***
360 * @return the strategy used to convert bean type names into element names
361 * @deprecated getNameMapper is split up in {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
362 */
363 public NameMapper getNameMapper() {
364 return getElementNameMapper();
365 }
366
367 /***
368 * Sets the strategy used to convert bean type names into element names
369 * @param nameMapper
370 * @deprecated setNameMapper is split up in {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
371 */
372 public void setNameMapper(NameMapper nameMapper) {
373 setElementNameMapper(nameMapper);
374 }
375
376
377 /***
378 * @return the strategy used to convert bean type names into element
379 * names. If no element mapper is currently defined then a default one is created.
380 */
381 public NameMapper getElementNameMapper() {
382 if ( elementNameMapper == null ) {
383 elementNameMapper = createNameMapper();
384 }
385 return elementNameMapper;
386 }
387
388 /***
389 * Sets the strategy used to convert bean type names into element names
390 * @param nameMapper
391 */
392 public void setElementNameMapper(NameMapper nameMapper) {
393 this.elementNameMapper = nameMapper;
394 }
395
396
397 /***
398 * @return the strategy used to convert bean type names into attribute
399 * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
400 */
401 public NameMapper getAttributeNameMapper() {
402 if (attributeNameMapper == null) {
403 attributeNameMapper = createNameMapper();
404 }
405 return attributeNameMapper;
406 }
407
408
409 /***
410 * Sets the strategy used to convert bean type names into attribute names
411 * @param nameMapper
412 */
413 public void setAttributeNameMapper(NameMapper nameMapper) {
414 this.attributeNameMapper = nameMapper;
415 }
416
417
418
419
420
421
422 // Implementation methods
423 //-------------------------------------------------------------------------
424
425 /***
426 * A Factory method to lazily create a new strategy to detect matching singular and plural properties
427 */
428 protected PluralStemmer createPluralStemmer() {
429 return new DefaultPluralStemmer();
430 }
431
432 /***
433 * A Factory method to lazily create a strategy used to convert bean type names into element names
434 */
435 protected NameMapper createNameMapper() {
436 return new DefaultNameMapper();
437 }
438
439 /***
440 * Attempt to lookup the XML descriptor for the given class using the
441 * classname + ".betwixt" using the same ClassLoader used to load the class
442 * or return null if it could not be loaded
443 */
444 protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
445 // trim the package name
446 String name = aClass.getName();
447 int idx = name.lastIndexOf( '.' );
448 if ( idx >= 0 ) {
449 name = name.substring( idx + 1 );
450 }
451 name += ".betwixt";
452
453 URL url = aClass.getResource( name );
454 if ( url != null ) {
455 try {
456 String urlText = url.toString();
457 if ( log.isDebugEnabled( )) {
458 log.debug( "Parsing Betwixt XML descriptor: " + urlText );
459 }
460 // synchronized method so this digester is only used by
461 // one thread at once
462 if ( digester == null ) {
463 digester = new XMLBeanInfoDigester();
464 digester.setXMLIntrospector( this );
465 }
466 digester.setBeanClass( aClass );
467 return (XMLBeanInfo) digester.parse( urlText );
468 }
469 catch (Exception e) {
470 log.warn( "Caught exception trying to parse: " + name, e );
471 }
472 }
473 return null;
474 }
475
476 /*** Loop through properties and process each one */
477 protected void addProperties(
478 BeanInfo beanInfo,
479 List elements,
480 List attributes)
481 throws
482 IntrospectionException {
483 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
484 if ( descriptors != null ) {
485 for ( int i = 0, size = descriptors.length; i < size; i++ ) {
486 addProperty(beanInfo, descriptors[i], elements, attributes);
487 }
488 }
489 if (log.isTraceEnabled()) {
490 log.trace(elements);
491 log.trace(attributes);
492 }
493 }
494
495 /***
496 * Process a property.
497 * Go through and work out whether it's a loop property, a primitive or a standard.
498 * The class property is ignored.
499 */
500 protected void addProperty(
501 BeanInfo beanInfo,
502 PropertyDescriptor propertyDescriptor,
503 List elements,
504 List attributes)
505 throws
506 IntrospectionException {
507 NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
508 .createDescriptor(propertyDescriptor,
509 isAttributesForPrimitives(),
510 this);
511 if (nodeDescriptor == null) {
512 return;
513 }
514 if (nodeDescriptor instanceof ElementDescriptor) {
515 elements.add(nodeDescriptor);
516 } else {
517 attributes.add(nodeDescriptor);
518 }
519 }
520
521 /*** Factory method to create XMLBeanInfo instances */
522 protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
523 XMLBeanInfo answer = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
524 return answer;
525 }
526
527 /*** Returns true if the type is a loop type */
528 public boolean isLoopType(Class type) {
529 return XMLIntrospectorHelper.isLoopType(type);
530 }
531
532
533 /*** Returns true for primitive types */
534 public boolean isPrimitiveType(Class type) {
535 return XMLIntrospectorHelper.isPrimitiveType(type);
536 }
537 /***
538 * By default it will be false.
539 *
540 * @return boolean if the beanInfoSearchPath should be used.
541 */
542 public boolean useBeanInfoSearchPath() {
543 return useBeanInfoSearchPath;
544 }
545
546 /***
547 * Specifies if you want to use the beanInfoSearchPath
548 * @see java.beans.Introspector for more details
549 * @param useBeanInfoSearchPath
550 */
551 public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
552 this.useBeanInfoSearchPath = useBeanInfoSearchPath;
553 }
554
555 }
This page was automatically generated by Maven